#region Copyright Notice
/*
Copyright (C) 2011 by Ashley Ross

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#endregion Copyright Notice

using System;
using System.IO;

namespace Manta.IO {
    /// <summary>
    /// Represents a junction.
    /// </summary>
    public sealed class JunctionInfo {
        private DirectoryInfo _alias;
        private DirectoryInfo _target;
        private string _printName;
        private bool _exists;


        private JunctionInfo() { }

        /// <summary>
        /// Initializes a new instance of the JunctionInfo class with the specified alias directory.
        /// </summary>
        /// <param name="aliasPath">The path to the alias directory.</param>
        public JunctionInfo(string aliasPath) : this(new DirectoryInfo(aliasPath)) { }

        /// <summary>
        /// Initializes a new instance of the JunctionInfo class with the specified alias directory.
        /// </summary>
        /// <param name="alias">The alias directory.</param>
        public JunctionInfo(DirectoryInfo alias) {
            if (alias == null)
                throw new ArgumentNullException("alias");

            if (alias == alias.Root)
                throw new ArgumentException("The alias parameter must be a non-root directory.", "alias");

            _alias = alias;
            this.Refresh();
        }

        internal JunctionInfo(DirectoryInfo alias, DirectoryInfo target, string printName, bool exists) {
            _alias = alias;
            _target = target;
            _printName = printName;
            _exists = exists;
        }


        /// <summary>
        /// Gets alias directory of the current junction.
        /// </summary>
        public DirectoryInfo Alias {
            get { return _alias; }
        }

        /// <summary>
        /// Gets target directory of the current junction.
        /// </summary>
        public DirectoryInfo Target {
            get { return _target; }
        }

        /// <summary>
        /// Gets print name of the current junction.
        /// </summary>
        public string PrintName {
            get { return _printName; }
        }

        /// <summary>
        /// Gets a value indicating wether the junction exists.
        /// </summary>
        public bool Exists {
            get { return _exists; }
        }

        /// <summary>
        /// Gets or sets the creation time set on the junction.
        /// </summary>
        public DateTime? CreationTime { get; set; }

        /// <summary>
        /// Gets or sets the modification time set on the junction.
        /// </summary>
        public DateTime? LastWriteTime { get; set; }

        /// <summary>
        /// Gets or sets the last access time set on the junction.
        /// </summary>
        public DateTime? LastAccessTime { get; set; }


        /// <summary>
        /// Creates a junction pointing to the specified target.
        /// </summary>
        /// <param name="target">The path to the target directory.</param>
        public void Create(string target) {
            // Defer invalid character parsing to the DirectoryInfo constructor
            this.Create(new DirectoryInfo(target), null);
        }

        /// <summary>
        /// Creates a junction pointing to the specified target with the specified print name.
        /// </summary>
        /// <param name="target">The path to the target directory.</param>
        /// <param name="printName">The display name for the junction.</param>
        public void Create(string target, string printName) {
            // Defer invalid character parsing to the DirectoryInfo constructor
            this.Create(new DirectoryInfo(target), printName);
        }

        /// <summary>
        /// Creates a junction pointing to the specified target.
        /// </summary>
        /// <param name="target">The target directory.</param>
        public void Create(DirectoryInfo target) {
            this.Create(target, target.Name);
        }

        /// <summary>
        /// Creates a junction pointing to the specified target with the specified print name.
        /// </summary>
        /// <param name="target">The path to the target directory.</param>
        /// <param name="printName">The display name for the junction.</param>
        public void Create(DirectoryInfo target, string printName) {
            Junction.Create(_alias, target, printName, CreationTime, LastWriteTime, LastAccessTime);
            _target = target;
            _printName = printName ?? target.FullName;
            _exists = true;
        }

        /// <summary>
        /// Deletes the alias directory of the junction.
        /// </summary>
        /// <remarks>This method deletes the alias directory even if it does not contain a junction; however, it will not delete non-empty directories.</remarks>
        public void Delete() {
            if (_exists) {
                Junction.Delete(_alias);
                _exists = false;
            }
        }

        /// <summary>
        /// Refreshes the state of the junction.
        /// </summary>
        public void Refresh() {
            ReparseDataBuffer data;
            string target;

            _target = null;
            _printName = string.Empty;
            _exists = false;

            _alias.Refresh();

            if (ReparsePoint.Exists(_alias)) {
                data = ReparsePoint.Get<ReparseDataBuffer>(_alias);

                if (data.Tag != ReparseTag.MountPoint)
                    return;

                target = data.SubstituteName;

                if (target.StartsWith(@"\??\"))
                    target = target.Substring(4, target.Length - 4);

                _target = new DirectoryInfo(target);
                _printName = data.PrintName;
                _exists = true;
            }
        }

        /// <summary>
        /// Returns a string describing the junction and its target.
        /// </summary>
        public override string ToString() {
            return _alias.FullName + " -> " + _target.FullName + " : " + _printName ?? string.Empty;
        }
    }
}